using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using StructureMap;

namespace CxRomos.Core.Services.Implementation {
    [Pluggable("Default")]
    public class Captcha : ICaptcha {
        private readonly Random _random = new Random();
        private Bitmap _image;

        #region ICaptcha Members

        public string FamilyName { get; set; }
        public string Text { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }

        public Bitmap Image {
            get {
                if (!string.IsNullOrEmpty(Text) && Height > 0 && Width > 0)
                    GenerateImage();
                return _image;
            }
        }

        //public Captcha(string s, int width, int height)
        //{
        //    this.text = s;
        //    this.SetDimensions(width, height);
        //    this.GenerateImage();
        //}

        //public Captcha(string s, int width, int height, string familyName)
        //{
        //    this.text = s;
        //    this.SetDimensions(width, height);
        //    this.SetFamilyName(familyName);
        //    this.GenerateImage();
        //}

        public void Dispose() {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        #endregion

        ~Captcha() {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing)
                // Dispose of the bitmap.
                _image.Dispose();
        }

        private void SetDimensions(int width, int height) {
            // Check the width and height.
            if (width <= 0)
                throw new ArgumentOutOfRangeException("width", width,
                                                      "Argument out of range, must be greater than zero.");
            if (height <= 0)
                throw new ArgumentOutOfRangeException("height", height,
                                                      "Argument out of range, must be greater than zero.");
            Width = width;
            Height = height;
        }

        private void SetFamilyName(string familyName) {
            try {
                var font = new Font(FamilyName, 16F);
                FamilyName = familyName;
                font.Dispose();
            }
            catch {
                FamilyName = FontFamily.GenericSerif.Name;
            }
        }

        public void GenerateImage() {
            // Create a new 32-bit bitmap image.
            var bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);

            // Create a graphics object for drawing.
            Graphics g = Graphics.FromImage(bitmap);
            g.SmoothingMode = SmoothingMode.AntiAlias;
            var rect = new Rectangle(0, 0, Width, Height);

            // Fill in the background.
            var hatchBrush = new HatchBrush(HatchStyle.SmallConfetti, Color.FromArgb(114, 172, 236),
                                            Color.FromArgb(161, 214, 255));
            //Color.FromArgb(76,136,198)    medium
            //Color.FromArgb(0,79,136)      dark
            //Color.FromArgb(114,172,236)   medium-light
            //Color.FromArgb(135,188,254)   light
            //Color.FromArgb(161,214,255)   really light

            g.FillRectangle(hatchBrush, rect);

            // Set up the text font.
            SizeF size;
            float fontSize = rect.Height + 4;
            Font font;
            // Adjust the font size until the text fits within the image.
            do {
                fontSize--;
                font = new Font(FamilyName, fontSize, FontStyle.Bold);
                size = g.MeasureString(Text, font);
            } while (size.Width > rect.Width);

            // Set up the text format.
            var format = new StringFormat();
            format.Alignment = StringAlignment.Center;
            format.LineAlignment = StringAlignment.Center;

            // Create a path using the text and warp it randomly.
            var path = new GraphicsPath();
            path.AddString(Text, font.FontFamily, (int) font.Style, font.Size, rect, format);
            const float v = 4F;
            PointF[] points =
                {
                    new PointF(_random.Next(rect.Width)/v, _random.Next(rect.Height)/v),
                    new PointF(rect.Width - _random.Next(rect.Width)/v, _random.Next(rect.Height)/v),
                    new PointF(_random.Next(rect.Width)/v, rect.Height - _random.Next(rect.Height)/v),
                    new PointF(rect.Width - _random.Next(rect.Width)/v, rect.Height - _random.Next(rect.Height)/v)
                };
            var matrix = new Matrix();
            matrix.Translate(0F, 0F);
            path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);

            // Draw the text.
            hatchBrush = new HatchBrush(HatchStyle.LargeConfetti, Color.FromArgb(0, 79, 136),
                                        Color.FromArgb(76, 136, 198));
            g.FillPath(hatchBrush, path);

            // Add some random noise.
            var m = Math.Max(rect.Width, rect.Height);
            for (var i = 0; i < (int) (rect.Width*rect.Height/30F); i++) {
                var x = _random.Next(rect.Width);
                var y = _random.Next(rect.Height);
                var w = _random.Next(m/50);
                var h = _random.Next(m/50);
                g.FillEllipse(hatchBrush, x, y, w, h);
            }

            // Clean up.
            font.Dispose();
            hatchBrush.Dispose();
            g.Dispose();

            // Set the image.
            _image = bitmap;
        }
    }
}